var getPerson = function(age, name) {
    return {
        age,
        name
    }
};

var ccf = getPerson(14, 'ccf');
console.log(ccf);
// ES6 允许直接写入变量和函数，作为对象的属性和方法。这时，属性名为变量名, 属性值为变量的值。
// 除了属性简写，方法也可以简写。
var obj = {
    method() {
        return "Hello!";
    }
};

// 模块输出变量，就非常合适使用简洁写法。
//var ms = {};
//
//function getItem (key) {
//    return key in ms ? ms[key] : null;
//}
//
//function setItem (key, value) {
//    ms[key] = value;
//}
//
//function clear () {
//    ms = {};
//}
//
//module.exports = { getItem, setItem, clear };
//// 等同于
//module.exports = {
//    getItem: getItem,
//    setItem: setItem,
//    clear: clear
//};


// 属性的赋值器（setter）和取值器（getter），事实上也是采用这种写法。
var cart = {
    _wheels: 4,

    get wheels () {
        return this._wheels;
    },

    set wheels (value) {
        if (value < this._wheels) {
            throw new Error('数值太小了！');
        }
        this._wheels = value;
    }
};

// 如果某个方法的值是一个Generator函数，前面需要加上星号。
var obj = {
    * m(){
        yield 'hello world';
    }
};

//属性名表达式
//JavaScript语言定义对象的属性，有两种方法。

// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;

// 方法一是直接用标识符作为属性名，方法二是用表达式作为属性名，这时要将表达式放在方括号之内。
// 如果使用字面量方式定义对象（使用大括号），在 ES5 中只能使用方法一（标识符）定义属性。
//var obj = {
//    foo: true,
//    abc: 123
//};

//ES6 允许字面量定义对象时，用方法二（表达式）作为对象的属性名，即把表达式放在方括号内。
var obj2 = {
    ['abc']: 12345,
    'def': 1230
};
console.log(obj2);
// 同样，表达式还可以用于定义对象中的函数名。

// 需要注意的是，属性名表达式与简洁表示法，不能同时使用，会报错。

// 报错
var foo = 'bar';
var bar = 'abc';
// var baz = { [foo] };

// 正确
var foo = 'bar';
var baz = { [foo]: 'baz.bar.value'};
console.log(baz.bar);

// 注意：属性名表达式如果是一个对象，默认情况下会自动将对象转为字符串[object Object]，这一点要特别小心。

const keyA = {a: 1};
const keyB = {b: 2};

const myObject = {
    [keyA]: 'valueA',
    [keyB]: 'valueB'
};

// myObject // Object {[object Object]: "valueB"}
// 上面代码中，[keyA]和[keyB]得到的都是[object Object]，所以[keyB]会把[keyA]覆盖掉，而myObject最后只有一个[object Object]属性。

// http://es6.ruanyifeng.com/#docs/object #3

// 3. 方法的 name 属性, 该属性返回函数名；
function functionNX() {
}
console.log(functionNX.name); // functionNX
// 如果对象的方法使用了取值函数（getter）和存值函数（setter），则name属性不是在该方法上面，
// 而是该方法的属性的描述对象的get和set属性上面，返回值是方法名前加上get和set。
//const obj = {
//    get foo() {},
//    set foo(x) {}
//};
//obj.foo.name
// TypeError: Cannot read property 'name' of undefined
//const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
//descriptor.get.name // "get foo"
//descriptor.set.name // "set foo"

//ES5比较两个值是否相等，只有两个运算符：相等运算符（==）和严格相等运算符（===）。
// 它们都有缺点，前者会自动转换数据类型，后者的NaN不等于自身，以及+0等于-0。JavaScript缺乏一种运算，在所有环境中，只要两个值是一样的，它们就应该相等。
//ES6提出“Same-value equality”（同值相等）算法，用来解决这个问题。Object.is就是部署这个算法的新方法。
// 它用来比较两个值是否严格相等，与严格比较运算符（===）的行为基本一致。

console.log(Object.is('foo', 'foo')); // true
console.log(Object.is({}, {})); // false
// 不同之处只有两个：一是+0不等于-0，二是NaN等于自身。
//+0 === -0 //true
//NaN === NaN // false
//Object.is(+0, -0) // false
//Object.is(NaN, NaN) // true

//ES5可以通过下面的代码，部署Object.is。
Object.defineProperty(Object, 'is', {
    value: function(x, y) {
        if (x === y) {
            // 针对+0 不等于 -0的情况
            return x !== 0 || 1 / x === 1 / y;
        }
        // 针对NaN的情况
        return x !== x && y !== y;
    },
    configurable: true,
    enumerable: false,
    writable: true
});

// Object.assign
//Object.assign方法用于对象的合并，将源对象（source）的所有可枚举属性，复制到目标对象（target）。

var target = { a: 1 };

var source1 = { b: 2 };
var source2 = { c: 3 };

Object.assign(target, source1, source2);
//Object.assign方法的第一个参数是目标对象，后面的参数都是源对象。
//注意，如果目标对象与源对象有同名属性，或多个源对象有同名属性，则后面的属性会覆盖前面的属性。
// 如果只有一个参数，Object.assign会直接返回该参数。
// 如果该参数不是对象，则会先转成对象，然后返回。
// 由于undefined和null无法转成对象，所以如果它们作为参数，就会报错。
// 如果非对象参数出现在源对象的位置（即非首参数），那么处理规则有所不同。
// 首先，这些参数都会转成对象，如果无法转成对象，就会跳过。这意味着，如果undefined和null不在首参数，就不会报错。

// Object.assign拷贝的属性是有限制的，只拷贝源对象的自身属性（不拷贝继承属性），也不拷贝不可枚举的属性（enumerable: false）。

Object.assign({b: 'c'},
    Object.defineProperty({}, 'invisible', {
        enumerable: false,
        value: 'hello'
    })
); // { b: 'c' }
// 上面代码中，Object.assign要拷贝的对象只有一个不可枚举属性invisible，这个属性并没有被拷贝进去。

//Object.assign方法实行的是浅拷贝，而不是深拷贝。也就是说，如果源对象某个属性的值是对象，那么目标对象拷贝得到的是这个对象的引用。

var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

//上面代码中，源对象obj1的a属性的值是一个对象，Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化，都会反映到目标对象上面。
//对于这种嵌套的对象，一旦遇到同名属性，Object.assign的处理方法是替换，而不是添加。

var target = { a: { b: 'c', d: 'e' } };
var source = { a: { b: 'hello' } };
Object.assign(target, source); // { a: { b: 'hello' } }

//常见用途
//（1）为对象添加属性

class Point {
    constructor(x, y) {
        Object.assign(this, {x, y});
    }
}
//上面方法通过Object.assign方法，将x属性和y属性添加到Point类的对象实例。

//（2）为对象添加方法

//Object.assign(SomeClass.prototype, {
//    someMethod(arg1, arg2) {
//···
//},
//anotherMethod() {
//···
//}
//});
//
// 等同于下面的写法
//SomeClass.prototype.someMethod = function (arg1, arg2) {
//    ···
//};
//SomeClass.prototype.anotherMethod = function () {
//    ···
//};
//上面代码使用了对象属性的简洁表示法，直接将两个函数放在大括号中，再使用assign方法添加到SomeClass.prototype之中。

//克隆对象
function clone(origin) {
    return Object.assign({}, origin);
}
//上面代码将原始对象拷贝到一个空对象，就得到了原始对象的克隆。
//不过，采用这种方法克隆，只能克隆原始对象自身的值，不能克隆它继承的值。如果想要保持继承链，可以采用下面的代码。
function clone(origin) {
    let originProto = Object.getPrototypeOf(origin);
    return Object.assign(Object.create(originProto), origin);
}

//合并多个对象
const merge = (target, ...sources) => Object.assign(target, ...sources);
//如果希望合并后返回一个新对象，可以改写上面函数，对一个空对象合并。
const merge = (...sources) => Object.assign({}, ...sources);


//为属性指定默认值
const DEFAULTS = {
    logLevel: 0,
    outputFormat: 'html'
};

function processContent(options) {
    options = Object.assign({}, DEFAULTS, options);
    console.log(options);
    // ...
}
//注意，由于存在浅拷贝的问题，DEFAULTS对象和options对象的所有属性的值，最好都是简单类型，不要指向另一个对象。否则，DEFAULTS对象的该属性很可能不起作用。

// 属性遍历
/*
ES6一共有5种方法可以遍历对象的属性。

（1）for...in

for...in循环遍历对象自身的和继承的可枚举属性（不含Symbol属性）。

（2）Object.keys(obj)

Object.keys返回一个数组，包括对象自身的（不含继承的）所有可枚举属性（不含Symbol属性）。

（3）Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一个数组，包含对象自身的所有属性（不含Symbol属性，但是包括不可枚举属性）。

（4）Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一个数组，包含对象自身的所有Symbol属性。

（5）Reflect.ownKeys(obj)

Reflect.ownKeys返回一个数组，包含对象自身的所有属性，不管属性名是Symbol或字符串，也不管是否可枚举。

以上的5种方法遍历对象的属性，都遵守同样的属性遍历的次序规则。

首先遍历所有属性名为数值的属性，按照数字排序。
其次遍历所有属性名为字符串的属性，按照生成时间排序。
最后遍历所有属性名为Symbol值的属性，按照生成时间排序。

Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]

上面代码中，Reflect.ownKeys方法返回一个数组，包含了参数对象的所有属性。
这个数组的属性次序是这样的，首先是数值属性2和10，其次是字符串属性b和a，最后是Symbol属性。
*/

